#include "Network.h"

#include <EUDP.h>

#include "DirectXStuff.h"
#include "Player.h"
#include "World.h"

Network *Network::g_instance = NULL;

void Network::Initalise(bool isServer)
{
#ifdef _DEBUG
	if(isServer)
	{
		std::cout << "Initalising Network as Server" << std::endl;
	} else {
		std::cout << "Initalising Network as Client" << std::endl;
	}
#endif

	m_isServer = isServer;

	//Set up socket									Valid Sockets: 50100 = 50129
	m_socket = new SocketEUDP();
	for(int port = 50100; port < 50130; ++port)
	{
		if( m_socket->CreateSocket(port) )
		{
			break;
		}
	}
}

void Network::ConnectTo(std::string ip, int port)
{
	//We only want to connect to somebody if we are a client, servers only accept connections
	ASSERT(!m_isServer);

	std::string log = ("Attempt connection to ");
	log += ip.c_str();
	Log( log.c_str() );

	//Check that we aren't already connected to another server
	if( m_connections.size() != 0 )
	{
		Log("Already connected to another server, disconnecting...");
		Disconnect();
	}

	//Attempt to connect to the server
	m_socket->ConnectTo(ip, port);
}

void Network::Disconnect()
{
	//Ensure that we are a client
	ASSERT(!m_isServer);

	//Check that we are connected to a server
	//TODO
}

void Network::UpdateTick(float deltaT)
{
	m_tickTimer += deltaT;
	while(m_tickTimer > 0.01f)
	{
		++m_currentTick;
		m_tickTimer -= 0.01f;
	}
}

void Network::AddNetworkController(NetworkController *controller)
{
	m_controllers.push_back(controller);
}

int Network::GetEventCount()
{
	return m_events.size();
}

GameMessage Network::GetNextEvent()
{
	if( !m_events.empty() )
	{
		GameMessage returnMessage( m_events.front() );
		m_events.pop();
		return returnMessage;
	}

	GameMessage nullMessage;
	nullMessage.messageType = NM_EMPTY;
	return nullMessage;
}

void Network::PreUpdate()
{
	//Update EUDP
	m_socket->Update();

	//If we're a client, check if a handshake has occured and if so, if they aren't already on our connection list
	if(!m_isServer)
	{
		std::map<int, SocketEUDP::ConnectionInfo> connectionInfo = m_socket->GetConnections();
		for(std::map<int, SocketEUDP::ConnectionInfo>::iterator i = connectionInfo.begin(); i != connectionInfo.end(); ++i)
		{
			//Handshake, check if we're already connected
			if(i->second.shakeCount >= 2)
			{
				bool onConnectionList = false;
				for( std::map<int, int>::iterator c = m_connections.begin(); c != m_connections.end(); ++c )
				{
					if(	c->first == i->first )
					{
						onConnectionList = true;
					}
				}

				if(!onConnectionList)
				{
					//Not connected, send the server a greeting message
					m_socket->Send(i->first, htons((short)NM_HELLO), NULL, 0, true);
				}
			}
		}
	}


	//While there are still messages waiting to be received
	while( m_socket->GetMessageCount() > 0 )
	{
		//Get the next message
		SocketEUDP::Message message = m_socket->GetMessages();
		message.messageType = ntohs(message.messageType);

		//If this is an invalid message, exit
		if(message.messageTypeEUDP == EUDP_M_EMPTY)
		{
#ifdef _DEBUG
			std::cout << "Network attempted to process and empty message";
#endif
			Log("Network attempted to process an empty message");
			break;
		}

		//Check if the EUDP reference is on our connections list, if not connected, send information to another function to process
		bool connected = false;
		for( std::map<int, int>::iterator i = m_connections.begin(); i != m_connections.end(); ++i)
		{
			if( message.reference == i->first )
			{
				connected = true;
			}
		}

		if(!connected)
		{
			//Server has received a message from a new client
			if(m_isServer)
			{
				//Got a message from a client, check that it's a connection message
				if( (NM)message.messageType == NM_HELLO )
				{
					//Create a new player for this client
					Mesh* tempMesh = new Mesh(g_pd3dDevice, "Cube.x");
					short eRef = World::Get().AddEntityAutoRef( new Player(World::Get().GetNextReference(), tempMesh, false) );

					//Add them to our connection list along with their entity reference
					m_connections.insert( std::pair<int, int>(message.reference, eRef) );

					//Notify the client of their new entity
					char data[MAX_MESSAGE_SIZE];
					data << *World::Get().m_entities[eRef];
					SendNewPlayerSingle(true, message.reference, data, World::Get().m_entities[eRef]->GetSize(), eRef);
					Log("SERVER - Sending new client their player");

					//Send the client all other entities that currently exist
					for( std::map<short, Entity*>::iterator i = World::Get().m_entities.begin(); i != World::Get().m_entities.end(); ++i)
					{
						if(i->first != eRef)
						{
							char eData[MAX_MESSAGE_SIZE];
							eData << *World::Get().m_entities[i->first];
							SendNewPlayerSingle(false, message.reference, eData, World::Get().m_entities[i->first]->GetSize(), i->first);
						}
					}

					//Notify all other clients of the new entity
					for( std::map<int, int>::iterator i = m_connections.begin(); i != m_connections.end(); ++i )
					{
						if( i->first != message.reference )
						{
							SendNewPlayerSingle(false, i->first, data, World::Get().m_entities[eRef]->GetSize(), eRef);
							Log("SERVER - Sending new player to all other entities");
						}
					}

				}

			//Client has received a message from a new server
			} else {
				//Got a message from a server
				if( (NM)message.messageType == NM_HELLO )
				{
					//Convert message into a new entity message
					NewEntityMessage nMessage = *(NewEntityMessage*)message.data;
					nMessage.entityReference = ntohs(nMessage.entityReference);

					//Add server to connection list
					m_connections.insert( std::pair<int, int>(message.reference, nMessage.entityReference) );

					//Get our player entity reference and add to the world
					Mesh* tempMesh = new Mesh(g_pd3dDevice, "Cube.x");
					World::Get().AddEntity(nMessage.entityReference, new Player(nMessage.entityReference, tempMesh, true) );
					nMessage.data >> *(World::Get().m_entities[nMessage.entityReference]);

					std::cout << "Client is creating an object\n";
				}
			}

		} else {
			//A valid message, convert message into a game message
			GameMessage newMessage;
			newMessage.messageType = (NM)message.messageType;
			for( int i = 0; i < (MAX_MESSAGE_SIZE - 1); ++i )
			{
				newMessage.data[i] = message.data[i];
			}

			//Check what type of message has been received
			switch( newMessage.messageType )
			{
			case NM_ENTITYDATA:
				//Data for an entity has been received, send to corresponding controller
				//Convert to a new message to find which entity this is bound to (need to convert to local endian format)
			
				{ //Require the scope to create variables inside a case
					//std::cout << "Got Entity Data" << std::endl;
				EntityMessage eMessage = *(EntityMessage*)newMessage.data;
				eMessage.entityReference = ntohs(eMessage.entityReference);
				SendToController(eMessage.entityReference, eMessage.tick, eMessage.data);
				}
				break;

			case NM_NEW_ENTITY:
				//An event has been received, store message for processing by game
				m_events.push( newMessage );
				break;

			case NM_SHOOT:
				{ //Require the scope to create variables inside a case
				EntityMessage eMessage = *(EntityMessage*)newMessage.data;
				eMessage.entityReference = ntohs(eMessage.entityReference);
				SendToController(eMessage.entityReference, eMessage.tick, eMessage.data);
				}

				m_events.push( newMessage );
				break;

			case NM_CURRENT_TICK:
				//Set the current tick
				{
				TickMessage tMessage = *(TickMessage*)newMessage.data;
				tMessage.currentTick = ntohl(tMessage.currentTick);
				m_currentTick = tMessage.currentTick;
				}
				break;

			default:
	#ifdef _DEBUG
				std::cout << "Invalid message type received: " << newMessage.messageType << std::endl;
	#endif
				Log("Invalid message type received");
				break;
			}
		}
	}
}

bool Network::SendToController(int entityRef, long tick, char *data)
{
	//Search through all Network Controllers we know about and see if it has a matching reference
	for(unsigned int i = 0; i < m_controllers.size(); ++i)
	{
		if( entityRef == m_controllers[i]->GetRef() )
		{
			m_controllers[i]->AddData(tick, data);
			return true;
		}
	}

	Log("Unable to match a network controller to a given entityReference");
	return false; //Unable to find a corresponding Network Controller
}

void Network::PostUpdate()
{
	//Send all messages waiting to be sent
	if( m_isServer )
	{
		//Server, send messages to all clients
		if( m_connections.size() != 0 )
		{
			while( m_messagesToSend.size() > 0 )
			{
				for( std::map<int, int>::iterator i = m_connections.begin(); i != m_connections.end(); ++i)
				{
					//Check the type of message we're sending
					switch( m_messagesToSend.front().type )
					{
					case NM_ENTITYDATA:
						//Entity data to send, don't send to a client if this is their entity
						if( m_messagesToSend.front().reference != i->second )
						{
							try
							{
								EUDP_R result = m_socket->Send( i->first, htons((short)NM_ENTITYDATA), m_messagesToSend.front().data, m_messagesToSend.front().length, false);
								if(result != EUDP_R_OK)
									throw result;
							}
							catch (EUDP_R result)
							{
								std::string message = "EUDP unable to send a message: ";
								message += result;
								Log( message.c_str() );
							}
						}
						break;

					default:
#ifdef _DEBUG
						std::cout << "Attempting to send an invalid message" << std::endl;
#endif
						Log("Attempting to send an invalid message");
						break;
					}
				}

				m_messagesToSend.pop();
			}

		}
		
	} else {
		//Client, send message to server
		if( m_connections.size() != 0 )
		{
			while( m_messagesToSend.size() > 0 )
			{
				try
				{
					EUDP_R result = m_socket->Send( m_connections.begin()->first, htons((short)NM_ENTITYDATA), m_messagesToSend.front().data, m_messagesToSend.front().length, false);
					if(result != EUDP_R_OK)
						throw result;
				}
				catch (EUDP_R result)
				{
					std::string message = "EUDP unable to send a message ";
					message += result;
					Log( message.c_str() );
				}

				m_messagesToSend.pop();
			}

		}
	}

	//Send the current tick to every client
	if( m_isServer )
	{
		for( std::map<int, int>::iterator i = m_connections.begin(); i != m_connections.end(); ++i)
		{
			MessageWaiting message;
			long l = htonl(m_currentTick);
			for(int a = 0; a < (sizeof(long)); ++a)
			{
				message.data[a] = ((char*) &l)[a];
			}
			m_socket->Send( i->first, htons((short)NM_CURRENT_TICK), message.data, sizeof(long), false);
		}
	}
}

void Network::SendEntity(char *data, int dataLength, short entityReference)
{
	//Entity information is to be sent, store in a message
	MessageWaiting message;
	message.type = NM_ENTITYDATA;
	message.reference = entityReference;
	message.length = dataLength;

	EntityMessage eM;
	//Copy entity reference
	eM.entityReference = htons(entityReference);
	eM.tick = m_currentTick;
	//Insert Data
	for(int i = 0; i < (dataLength); ++i)
	{
		eM.data[i] = data[i];
	}

	for(int i = 0; i < MAX_MESSAGE_SIZE; ++i)
	{
		message.data[i] = ((char*)&eM)[i];
	}

	m_messagesToSend.push(message);
}

void Network::SendNewPlayer(char *data, int dataLength, short entityReference)
{
	//TODO
}

void Network::SendShot(char *data, int dataLength, short entityReference)
{
	EntityMessage eM;
	//Copy entity reference
	eM.entityReference = htons(entityReference);
	eM.tick = m_currentTick;
	//Insert Data
	for(int i = 0; i < (dataLength); ++i)
	{
		eM.data[i] = data[i];
	}

	GameMessage mM;
	for(int i = 0; i < MAX_MESSAGE_SIZE; ++i)
	{
		mM.data[i] = ((char*)&eM)[i];
	}

	//Send to server
	m_socket->Send(m_connections.begin()->first, htons((short)NM_SHOOT), mM.data, dataLength, true);
}

void Network::SendNewPlayerSingle(bool greet, int clientRef, char *data, int dataLength, short entityReference)
{
	//Create the message to be sent
	GameMessage message;
	NewEntityMessage eM;

	//Set Type
	if(greet)
	{
		message.messageType = (NM)htons((short)NM_HELLO);
	} else {
		message.messageType = (NM)htons((short)NM_NEW_ENTITY);
	}

	//Copy entity reference
	short e = htons(entityReference);

	for( int i = 0; i < sizeof(short); ++i)
	{
		eM.entityReference = e;
	}
	//Copy data
	for(int i = 0; i < (dataLength); ++i)
	{
		eM.data[i] = data[i];
	}

	//Copy NewEntityMessage into message to send
	for(int i = 0; i < MAX_MESSAGE_SIZE; ++i)
	{
		message.data[i] = ((char*)&eM)[i];
	}

	//Send to client
	m_socket->Send(clientRef, message.messageType, message.data, ( dataLength + sizeof(short) ), false); //TEMP
}

short Network::GetThisClientReference()
{
	return m_connections.begin()->second;
}